//
// Created by root on 17-3-1.
//
#include <sys/stat.h>
#include <fstream>
#include <cstring>
#include <SQLiteCpp/SQLiteCpp.h>
#include "sand.h"
#include "json.h"
#include "traffic_data_process.h"
#include "spdlog/spdlog.h"

auto console = spdlog::stdout_logger_mt("console");

TrafficProcess::TrafficProcess() {
    spdlog::set_level(spdlog::level::debug);
}

TrafficProcess::~TrafficProcess() {

}

void TrafficProcess::set_yaml_config(Yaml_Config_ST config_st) {
    _yaml_config_st.db_file = config_st.db_file;
    _yaml_config_st.data_file = config_st.data_file;
    _yaml_config_st.collect_interval = config_st.collect_interval;
    _yaml_config_st.dump_at_minute = config_st.dump_at_minute;
}

TrafficProcess::Yaml_Config_ST TrafficProcess::get_yaml_config() {
    return _yaml_config_st;
}

void TrafficProcess::set_traffic_data_map(Traffic_Data_ST data_st) {
    _traffic_data_st.target = data_st.target;
    _traffic_data_st.data_type = data_st.data_type;
    _traffic_data_st.initial_time = data_st.initial_time;
    _traffic_data_st.start_time = data_st.start_time;
    _traffic_data_st.end_time = data_st.end_time;

    std::string target_key = _traffic_data_st.target;
    auto find_target = _traffic_data_map.find(target_key);
    if(find_target != _traffic_data_map.end()) {
        find_target->second.data_type = data_st.data_type;
        find_target->second.initial_time = data_st.initial_time;
        find_target->second.start_time = data_st.start_time;
        find_target->second.end_time = data_st.end_time;

        if (data_st.data_type == TRAFFIC_UPLOAD) {
            for (auto it = data_st.data_map.begin(); it != data_st.data_map.end(); ++it) {
                auto find_time = find_target->second.data_map.find(it->first);
                if (find_time != find_target->second.data_map.end()) {
                    find_time->second.upload = it->second.upload;
                } else {
                    find_target->second.data_map[it->first].upload = it->second.upload;
                }
            }
        }
        if (data_st.data_type == TRAFFIC_DOWNLOAD) {
            for (auto it = data_st.data_map.begin(); it != data_st.data_map.end(); ++it) {
                auto find_time = find_target->second.data_map.find(it->first);
                if (find_time != find_target->second.data_map.end()) {
                    find_time->second.download = it->second.download;
                } else {
                    find_target->second.data_map[it->first].download = it->second.download;
                }
            }
        }
    } else {
        _traffic_data_st.data_map.insert(data_st.data_map.begin(), data_st.data_map.end());
        _traffic_data_map[target_key] = _traffic_data_st;
    }
}

TrafficProcess::Traffic_Data_Map TrafficProcess::get_traffic_data_map() {
    return _traffic_data_map;
}

void TrafficProcess::data_parse(std::string filename) {
    uint32_t line_idx = 0;
    std::ifstream file(filename);
    std::string line;

    while (std::getline(file, line)) {
        if (line.empty()) continue;

        uint32_t  block_idx = line_idx % 5;
        if (block_idx == BLOCK_TARGET) {
            memset(&_traffic_data_st, 0x0, sizeof(_traffic_data_st));

            uint32_t loop_idx = 0;
            char sep = ' ';
            std::string expected_str;
            for(size_t p=0, q=0; p!=line.npos; p=q) {
                expected_str = line.substr(p+(p!=0), (q=line.find(sep, p+1))-p-(p!=0));
                if (loop_idx == 0 ) {
                    std::string::size_type find_upload = expected_str.find("upload");
                    if (find_upload != std::string::npos) {
                        _traffic_data_st.data_type = TRAFFIC_UPLOAD;
                    }
                    std::string::size_type find_download = expected_str.find("download");
                    if (find_download != std::string::npos) {
                        _traffic_data_st.data_type = TRAFFIC_DOWNLOAD;
                    }
                } else {
                    _traffic_data_st.target = expected_str;
                }
                loop_idx++;
            }
        } else if (block_idx == BLOCK_INITIAL_TIME) {
            _traffic_data_st.initial_time = std::strtoll(line.c_str(), NULL, 10);
        } else if (block_idx == BLOCK_START_TIME) {
            _traffic_data_st.start_time = std::strtoll(line.c_str(), NULL, 10);
        } else if (block_idx == BLOCK_END_TIME) {
            _traffic_data_st.end_time = std::strtoll(line.c_str(), NULL, 10);
        } else if (block_idx == BLOCK_DATA) {
            uint32_t loop_idx = 0;
            uint64_t start_time_idx = _traffic_data_st.start_time;
            Data_Map _data_map = _traffic_data_st.data_map;
            char sep = ',';
            std::string expected_str;
            if (_traffic_data_st.data_type == TRAFFIC_UPLOAD) {
                for(size_t p=0, q=0; p!=line.npos; p=q) {
                    expected_str = line.substr(p+(p!=0), (q=line.find(sep, p+1))-p-(p!=0));
                    uint64_t item = std::strtoll(expected_str.c_str(), NULL, 10);
                    if (loop_idx > 0) {
                        _data_map[start_time_idx].upload = item;
                        start_time_idx = start_time_idx + 60;
                    }
                    loop_idx++;
                }
            }

            if (_traffic_data_st.data_type == TRAFFIC_DOWNLOAD) {
                for(size_t p=0, q=0; p!=line.npos; p=q) {
                    expected_str = line.substr(p+(p!=0), (q=line.find(sep, p+1))-p-(p!=0));
                    uint64_t item = std::strtoll(expected_str.c_str(), NULL, 10);
                    if (loop_idx > 0) {
                        _data_map[start_time_idx].download = item;
                        start_time_idx = start_time_idx + 60;
                    }
                    loop_idx++;
                }
            }

            _traffic_data_st.data_map = _data_map;
            set_traffic_data_map(_traffic_data_st);
        }
        line_idx++;
    }

}

void TrafficProcess::data_to_memory() {
    /*SQLite::Database db(":memory:", SQLite::OPEN_READWRITE);
    db.exec("CREATE TABLE minute_flow(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                    "owner TEXT NOT NULL, "
                    "datetime TEXT NOT NULL, "
                    "upload INTEGER, d"
                    "ownload INTEGER);");

    TrafficProcess::Traffic_Data_Map traffic_map;
    traffic_map = get_traffic_data_map();
    for (auto it = traffic_map.begin(); it != traffic_map.end(); ++it) {
        for (auto itr = it->second.data_map.begin(); itr != it->second.data_map.end(); ++itr) {
            std::string datetime_str = sand::format((itr->first +8*3600)*1000,"yyyy-mm-dd HH:MM:SS");
            std::string insert_sql;
            if (it->second.data_type > 0) {
                insert_sql= ("INSERT INTO minute_flow(owner, datetime, download) VALUES (\"" + it->first + "\"" + ",\"" + datetime_str + "\"" + "," + std::to_string(itr->second) + ")");
            } else {
                insert_sql= ("INSERT INTO minute_flow(owner, datetime, upload) VALUES (\"" + it->first + "\"" + ",\"" + datetime_str + "\"" + "," + std::to_string(itr->second) + ")");
            }
            db.exec(insert_sql);
        }
    }

    SQLite::Statement query(db, "select * from minute_flow;");
    while (query.executeStep()) {
        uint32_t id = query.getColumn(0).getUInt();
        std::string ip = query.getColumn(1).getString();
        std::string datetime = query.getColumn(2).getString();
        uint32_t upload = query.getColumn(3).getUInt();
        uint32_t download = query.getColumn(4).getUInt();
        std::cout<<"minute flow, id: "<<id<<", ip: "<<ip<<", timedate: "<<datetime<<", upload: "<<upload<<", download: "<<download<<std::endl;
    }
    */

    int ret;
    std::string dbfile = _yaml_config_st.db_file;
    std::string datafile = _yaml_config_st.data_file;

    std::string cmd_str = "bw_get -i bdist1-upload-minute-15 -h -m > " +
                          datafile + " &&  bw_get -i bdist1-download-minute-15 -h -m >> " + datafile;
    ret = std::system(cmd_str.c_str());

    struct stat st;
    if(stat(datafile.c_str(), &st) != 0) {
        //return;
    }

    if (st.st_size > 0) {
        data_parse(datafile);

        try
        {
            SQLite::Database db(dbfile, SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE);
            if (!db.tableExists("minute_flow")) {
                std::string create_sql = "CREATE TABLE minute_flow(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                        "owner TEXT NOT NULL, datetime TEXT NOT NULL, upload INTEGER, download INTEGER)";
                ret = db.exec(create_sql);
                console->info(create_sql);
            }

            SQLite::Transaction transaction(db);
            TrafficProcess::Traffic_Data_Map traffic_map;
            traffic_map = get_traffic_data_map();
            for (auto it = traffic_map.begin(); it != traffic_map.end(); ++it) {
                for (auto itr = it->second.data_map.begin(); itr != it->second.data_map.end(); ++itr) {
                    bool bExists = false;
                    bool bUpdate = false;
                    std::string owner;
                    std::string datetime;
                    uint32_t upload;
                    uint32_t download;
                    std::string datetime_str = sand::format((itr->first +8*3600)*1000,"yyyy-mm-dd HH:MM:SS");
                    std::string select_sql = "select owner, datetime, upload, download from minute_flow where owner = '" +
                                             it->second.target + "' and datetime = '" + datetime_str + "';";
                    //console->info(select_sql);
                    SQLite::Statement query(db, select_sql);
                    while (query.executeStep()) {
                        bExists = true;
                        owner = query.getColumn(0).getString();
                        datetime = query.getColumn(1).getString();
                        upload = query.getColumn(2).getUInt();
                        download = query.getColumn(3).getUInt();
                        if (itr->second.upload > upload) {
                            bUpdate = true;
                            upload = itr->second.upload;
                        }
                        download = query.getColumn(3).getUInt();
                        if (itr->second.download > download) {
                            bUpdate = true;
                            download = itr->second.download;
                        }
                    }
                    if (!bExists) {
                        std::string insert_sql = "INSERT INTO minute_flow(owner, datetime, upload, download) VALUES ('"+ it->first +
                                                 "', '" + datetime_str + "', " + std::to_string(itr->second.upload) + ", " + std::to_string(itr->second.download) + ");";
                        db.exec(insert_sql);
                        console->info(insert_sql);
                    } else {
                        if (bUpdate) {
                            std::string update_sql = "update minute_flow set upload = " + std::to_string(upload) +
                                                     ",  download = " + std::to_string(download) + " where owner = '" + it->second.target + "' and datetime = '" + datetime_str + "';";
                            db.exec(update_sql);
                            console->info(update_sql);
                        }
                    }
                }
            }
            transaction.commit();
        }
        catch (std::exception& e)
        {
            console->error("data_to_memory exception: {0}", e.what());
        }

        TrafficProcess::Traffic_Data_Map data_map;
        data_map = get_traffic_data_map();
        data_map.clear();
    }
}

void TrafficProcess::data_to_temp() {
    console->info("data to temp");
}

void TrafficProcess::data_store_by_hour() {
    int64_t this_time = sand::now();
    int that_hour;
    int this_hour = sand::hour(this_time);
    if (this_hour == 0)
        that_hour = 23;
    else
        that_hour = this_hour - 1;

    int64_t start_datetime = sand::datetime(sand::year(this_time),
                                            sand::month(this_time),
                                            sand::day(this_time),
                                            that_hour, 0, 0);
    int64_t end_datetime = sand::datetime(sand::year(this_time),
                                          sand::month(this_time),
                                          sand::day(this_time),
                                          that_hour, 59, 59);
    std::string start_datetime_str = sand::str(start_datetime);
    std::string end_datetime_str = sand::str(end_datetime);

    std::string select_sql = "select owner, sum(upload), sum(download) from minute_flow where datetime > '" +
            start_datetime_str + "' and datetime < '" + end_datetime_str + "' group by owner;";
    console->info(select_sql);

    try
    {
        std::string dbfile = _yaml_config_st.db_file;
        SQLite::Database db(dbfile, SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE);
        SQLite::Transaction transaction(db);
        if (!db.tableExists("hour_flow")) {
            std::string create_sql = "CREATE TABLE hour_flow(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                    "owner TEXT NOT NULL, datetime TEXT NOT NULL, upload INTEGER, download INTEGER);";
            db.exec(create_sql);
            console->info(create_sql);
        }
        SQLite::Statement query(db, select_sql);
        while (query.executeStep()) {
            std::string owner = query.getColumn(0).getString();
            uint32_t upload = query.getColumn(1).getUInt();
            uint32_t download = query.getColumn(2).getUInt();

            std::string insert_sql = "insert into hour_flow(owner, datetime, upload, download) select '" +
                    owner + "', '" + start_datetime_str + "', " + std::to_string(upload) + ", " + std::to_string(download) +
                    " where not exists(select * from hour_flow where owner = '" + owner + "' and  datetime = '" + start_datetime_str + "');";
            db.exec(insert_sql);
            console->info(insert_sql);
        }
        transaction.commit();
    }
    catch (std::exception& e)
    {
        console->error("data_store_by_hour exception: {0}", e.what());
    }
}

void TrafficProcess::data_store_by_day() {
    int64_t this_time = sand::now();
    int this_hour = sand::hour(this_time);

    int64_t start_datetime = sand::datetime(sand::year(this_time),
                                            sand::month(this_time),
                                            sand::day(this_time),
                                            0, 0, 0);
    int64_t end_datetime = sand::datetime(sand::year(this_time),
                                          sand::month(this_time),
                                          sand::day(this_time),
                                          23, 59, 59);
    std::string start_datetime_str = sand::str(start_datetime);
    std::string end_datetime_str = sand::str(end_datetime);

    std::string select_sql = "select owner, sum(upload), sum(download) from hour_flow where datetime > '" +
                             start_datetime_str + "' and datetime < '" + end_datetime_str + "' group by owner;";
    console->info(select_sql);

    try
    {
        std::string dbfile = _yaml_config_st.db_file;
        SQLite::Database db(dbfile, SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE);
        SQLite::Transaction transaction(db);
        if (!db.tableExists("day_flow")) {
            std::string create_sql = "CREATE TABLE day_flow(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                    "owner TEXT NOT NULL, datetime TEXT NOT NULL, upload INTEGER, download INTEGER);";
            db.exec(create_sql);
            console->info(create_sql);
        }
        SQLite::Statement query(db, select_sql);
        while (query.executeStep()) {
            std::string owner = query.getColumn(0).getString();
            uint32_t upload = query.getColumn(1).getUInt();
            uint32_t download = query.getColumn(2).getUInt();

            std::string insert_sql = "insert into day_flow(owner, datetime, upload, download) select '" +
                    owner + "', '" + start_datetime_str + "', " + std::to_string(upload) + ", " + std::to_string(download) +
                    " where not exists(select * from day_flow where owner = '" + owner + "' and  datetime = '" + start_datetime_str + "');";
            db.exec(insert_sql);
            console->info(insert_sql);
        }
        transaction.commit();
    }
    catch (std::exception& e)
    {
        console->error("data_store_by_day exception: {0}", e.what());
    }
}